
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Collections;

namespace LowLevelGraphics.Filter
{
    /// <summary>
    /// Distance Transform: 
    /// This works now with m_bFastMode = false, but only if the pic is b/w-monochrome
    /// if m_bFastMode is true also gray level pics should work now. 
    /// Distance is an algorithm to create a distance transform grey level image
    /// from a binary image.
    /// </summary>
    public class DistanceTransform : BaseImageFilter
    {
        protected bool m_bFastMode = true;
        /// <summary>
        ///  2D array representing the new grey level image produced.
        /// </summary>
        protected int[,] m_aGreyLevel = null;

        protected double m_dOffset = 0.0f;
        protected double m_dScale = 1.0f;
        protected int m_nIterations = 1;
        /// <summary>
        /// Hash table representing original foreground edge pixels.
        /// </summary>
        protected HashSet m_OldForegroundEdge = null;

        /// <summary>
        /// Hash table representing original background edge pixels.
        /// </summary>
        protected HashSet m_OldBackgroundEdge = null;

        /// <summary>
        /// Default no-args constructor.
        /// </summary>
        public DistanceTransform()
        {
        }

        /// <summary>
        /// constructor to specify scale and offset directly
        /// </summary>
        /// <param name="_dScale"></param>
        /// <param name="_dOffset"></param>
        public DistanceTransform(double _dScale, double _dOffset)
        {
            this.m_dScale = _dScale;
            this.m_dOffset = _dOffset;
        }

        /// <summary>
        /// used by clone to create a deep copy
        /// </summary>
        /// <param name="_distanceTransform"></param>
        internal DistanceTransform(DistanceTransform _distanceTransform)
        {
            this.m_dOffset = _distanceTransform.m_dOffset;
            this.m_dScale = _distanceTransform.m_dScale;
            this.m_bFastMode = _distanceTransform.m_bFastMode;
        }

        /// <summary>
        /// Gets or sets the offset
        /// </summary>
        public double Offset
        {
            get { return m_dOffset; }
            set { m_dOffset = value; }
        }

        /// <summary>
        /// Gets or sets the iterations.
        /// </summary>
        /// <value>The iterations.</value>
        public int Iterations
        {
            get { return m_nIterations; }
            set { m_nIterations = value; }
        }

        /// <summary>
        /// Executes this filter
        /// </summary>
        /// <param name="_bitmap"></param>
        public override UnsafeBitmap Execute(UnsafeBitmap _bitmap)
        {
            UnsafeBitmap bitmap = _bitmap;

            //Use this for colored or gray level pics
            if (m_bFastMode)
            {
                for (int i = 0; i < m_nIterations; i++)
                {
                    DistanceTransformFastMode(_bitmap);
                }
            }
            else
            {
                //use this for monochrome b/w pics
                bitmap.InitializeEdgePixels();
                DistanceImage(bitmap, m_dScale, m_dOffset);
            }
            return _bitmap;
        }

        /// <summary>
        /// Performs a single thinning and colours the thinned pixels accordingly.
        /// </summary>
        /// <param name="binary">The BinaryFast representation of the input image.</param>
        /// <param name="i">The grey-level with which to colour the thinned pixels.</param>
        /// <returns>The BinaryFast representation of the thinned image.</returns>
        public UnsafeBitmap DistanceSingleIteration(UnsafeBitmap binary, int i)
        {
            IEnumerator it = null;
            Point p = Point.Empty;
            it = binary.m_ForegroundEdgePixels.GetEnumerator();
            while (it.MoveNext())
            {
                p = (Point)it.Current;
                binary.m_BackgroundEdgePixels.Add(p);
                binary.RemovePixel(p);
                m_aGreyLevel[p.X, p.Y] = i;
            }
            binary.GenerateForegroundEdgeFromBackgroundEdge();
            return binary;
        }

        /// <summary>
        /// Takes a BinaryFast representation and produces a distance transform
        /// with the grey-level image stored in the BinaryFast pixels 2D array.
        /// </summary>
        /// <param name="binary">binary The input image.</param>
        /// <param name="_dScale">The _d scale.</param>
        /// <param name="_dOffset">The _d offset.</param>
        /// <returns>The output grey-level image.</returns>
        public UnsafeBitmap DistanceImage(UnsafeBitmap binary,
                          double _dScale, double _dOffset)
        {
            m_aGreyLevel = new int[binary.Width, binary.Height];
            int i = 1;
            int grey = 0;
            int color = 0;
            m_OldForegroundEdge = new HashSet(binary.m_ForegroundEdgePixels);
            m_OldBackgroundEdge = new HashSet(binary.m_BackgroundEdgePixels);

            int nHeight = binary.Height;
            int nWidth = binary.Width;

            int n = 0;
            int m = 0;

            for (n = 0; n < nHeight; ++n)
            {
                for (m = 0; m < nWidth; ++m)
                {
                    m_aGreyLevel[m, n] = 0;
                }
            }

            while (binary.m_ForegroundEdgePixels.Count > 0)
            {
                binary = DistanceSingleIteration(binary, i);
                ++i;
            }

            if (i < 2) i = 2;//Stops division by 0 in bad cases due to non-binary images
            for (n = 0; n < nHeight; ++n)
            {
                for (m = 0; m < nWidth; ++m)
                {
                    //grey = greyLevel[m][n] * (255/(i-1));
                    grey = (int)Math.Round(m_aGreyLevel[m, n] * _dScale + _dOffset);
                    //System.out.println(grey);
                    if (grey > 255)
                    {
                        grey = 255;
                    }
                    else if (grey < 0)
                    {
                        grey = 0;
                    }
                    color = Color.FromArgb(grey, grey, grey).ToArgb();
                    binary.SetPixel(m, n, Color.FromArgb(color));
                }
            }

            binary.m_ForegroundEdgePixels = new HashSet(m_OldForegroundEdge);
            binary.m_BackgroundEdgePixels = new HashSet(m_OldBackgroundEdge);

            return binary;
        }

        /// <summary>
        /// Squares the specified q.
        /// </summary>
        /// <param name="q">The q.</param>
        /// <returns></returns>
        public int Square(int q)
        {
            return q * q;
        }

        /// <summary>
        /// Calculates distance transform in a fast mode.
        /// </summary>
        /// <param name="f">The f.</param>
        /// <param name="n">The n.</param>
        /// <returns></returns>
        public float[] DistanceTransformFastMode(float[] f, int n)
        {
            float[] d = new float[n];
            int[] v = new int[n];
            float[] z = new float[n + 1];
            int k = 0;
            v[0] = 0;
            z[0] = float.NegativeInfinity; //-INF;
            z[1] = float.PositiveInfinity; //+INF;
            for (int q = 1; q <= n - 1; q++)
            {
                float s = ((f[q] + Square(q)) - (f[v[k]] + Square(v[k]))) / (2 * q - 2 * v[k]);
                while (s <= z[k])
                {
                    k--;
                    s = ((f[q] + Square(q)) - (f[v[k]] + Square(v[k]))) / (2 * q - 2 * v[k]);
                }
                k++;
                v[k] = q;
                z[k] = s;
                z[k + 1] = +float.PositiveInfinity;
            }

            k = 0;
            for (int q = 0; q <= n - 1; q++)
            {
                while (z[k + 1] < q)
                    k++;
                d[q] = Square(q - v[k]) + f[v[k]];
            }

            return d;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="im"></param>
        public void DistanceTransformFastMode(UnsafeBitmap im)
        {
            int width = im.Width;
            int height = im.Height;
            float[] f = new float[Math.Max(width, height)];

            // transform along columns
            for (int x = 0; x < width; x++)
            {
                for (int y = 0; y < height; y++)
                {
                    f[y] = (float)im[x, y, true].R;
                }
                float[] d = DistanceTransformFastMode(f, height);
                for (int y = 0; y < height; y++)
                {
                    im.SetGrayPixel(x, y, (int)d[y]);
                }
            }

            // transform along rows
            for (int y = 0; y < height; y++)
            {
                for (int x = 0; x < width; x++)
                {
                    f[x] = (float)im[x, y, true].R;
                }
                float[] d = DistanceTransformFastMode(f, width);
                for (int x = 0; x < width; x++)
                {
                    im.SetGrayPixel(x, y, (int)d[x]);
                }
            }
        }

        /// <summary>
        /// used to create a deep copy
        /// </summary>
        /// <returns></returns>
        public override object Clone()
        {
            return new DistanceTransform(this);
        }
    }
}